//
//  HOMCArrayIterations.m
//  HigherOrderMessaging
//
//  Created by Ofri Wolfus on 18/09/06.
//  Copyright 2006 Ofri Wolfus. All rights reserved.
//

#import "HOMCArrayIterations.h"
#import "Message.h"
#import "HOMIteratedArgument.h"
#import "HOMUtilities.h"
#include <objc/objc-class.h>
#include <objc/objc-runtime.h>
#include <malloc/malloc.h>


extern Method _hom_findMethodForSelector(SEL);

id hom_collectv(id *objects, unsigned count, BOOL cleanup, Class resultingCollection, Message *msg) {
	SEL				sel = [msg selector];
	marg_list		origArgs = [msg arguments];
	unsigned		argsSize = [msg argumentsSize];
	marg_list		args = malloc(malloc_size(origArgs));
	unsigned		argCount = hom_getNumberOfArguments(sel);
	unsigned		i;
	Class			iteratedArgCls = [HOMIteratedArgument class];
	char			*types = calloc(argCount, sizeof(char));
	Method			meth = _hom_findMethodForSelector(sel);
	NSMutableArray	*result = nil;	// This may actually be a mutable set or any other collection
	IMP				addObject = NULL;
	
	if (*(method_getTypeEncoding(meth)) == _C_ID || *(method_getTypeEncoding(meth)) == _C_CLASS) {
		result = [[[resultingCollection alloc] initWithCapacity:count] autorelease];
		addObject = [result methodForSelector:@selector(addObject:)];
	}
	
	// First, copy the arguments list since we're going to modify it
	memcpy(args, origArgs, malloc_size(args));
	
	// Scan the arguments of our message and find instances of HOMIteratedArgument.
	// Our types array will mark the places of these with the letter 'z' so we don't have to check again.
	// This loop also finds the largest number of arguments that needs to be iterated, and stores it in count.
	for (i = 2U; i < argCount; i++) {
		types[i] = *hom_getArgumentTypeAtIndex(meth, i);
		if (types[i] == _C_ID) {
			if ([marg_getValue(args, hom_getArgumentOffsetAtIndex(meth, i), id) isKindOfClass:iteratedArgCls])
				types[i] = 'z';	// This is our special mark for an iterated argument
		}
	}
	
	// Loop through all objects in all our iterated arguments, update our args list, and resend it
	for (i = 0U; i < count; i++) {
		unsigned j;
		
			// Update the remaining arguments in our list
		for (j = 2U; j < argCount; j++) {
			if (types[j] == 'z') {
				int offset = hom_getArgumentOffsetAtIndex(meth, j);
				HOMIteratedArgument *arg = marg_getValue(origArgs, offset, HOMIteratedArgument *);
				
				marg_setValue(args, offset, id, [arg nextObject]);
				
				// When there are no arguments left for this argument iterator, we mark its type as id
				// so we'll skip it in the next run.
				if ([arg isFinished])
					types[j] = _C_ID;
			}
		}
		
		// Send the updated arguments list
		addObject(result, @selector(addObject:), objc_msgSendv(objects[i], sel, argsSize, args));
	}
	
	// Clean up
	free(types);
	free(args);
	if (cleanup)
		free(objects);
	
	return result;
}

/*
 * NOTE: Although hom_returnWhere() and hom_returnSingleWhere() are defined as static inline, they can never be inlined.
 * When support for inlined functions that take arguments list is implemented, uncomment the line which says they should always be inlined.
 */

//static inline id hom_returnWhere(id *objects, unsigned count, BOOL resultVal, Message *firstMsg, va_list additionalMessages) __attribute__((always_inline));
static inline id hom_returnWhere(id *objects, unsigned count, BOOL cleanup, Class resultingCollection, BOOL resultVal, Message *firstMsg, va_list additionalMessages) {
	Message				*msg;
	unsigned			i;
	NSMutableArray		*result = [[resultingCollection alloc] init];	// This may actually be a mutable set or any other collection
	IMP					addObject = [result methodForSelector:@selector(addObject:)];
	va_list				messages;
	
	
	// Loop through all our objects, and send it all the messages passed to us.
	// All messages except the last are assumed to return id, and the last one should return a BOOL
	// (although any integer value will work).
	for (i = 0U; i < count; i++) {
		id	obj = objects[i];
		
		// Send every message except the last one to our object while setting obj
		// to be the result of the last message.
		// NOTE: We don't verify that the receiver responds to the actual message like -select does
		// which give us a nice speed boost, but may also cause an exception to be thrown.
		// TODO: Cache the IMPs of -[Message selector], -[Message argumentsSize] and -[Message arguments].
		//for (j = 0U; j < (msgCount - 1U); j++) {
		obj = objc_msgSendv(obj, [firstMsg selector], [firstMsg argumentsSize], [firstMsg arguments]);
		
		va_copy(messages, additionalMessages);
		
		while ((msg = va_arg(messages, Message *)))
			obj = objc_msgSendv(obj, [msg selector], [msg argumentsSize], [msg arguments]);
		
		va_end(messages);
		
		//msg = objectAtIndexIMP(messages, @selector(objectAtIndex:), msgCount - 1);
		
		//if (((hom_BOOL_msgSendv)objc_msgSendv)(obj, [msg selector], [msg argumentsSize], [msg arguments]) == resultValue)
		//	[result addObject:object];
		if ((BOOL)(int)obj == resultVal)
			addObject(result, @selector(addObject:), objects[i]);
	}
	
	// Clean up
	if (cleanup) {
		free(objects);
		va_end(additionalMessages);
	}
	
	// Make our return value pretty
	if ([result count] == 0) {
		[result release];
		result = nil;
	} else {
		result = [result autorelease];
	}
	
	return result;
}

id hom_selectWhere(id *objects, unsigned count, BOOL cleanup, Class resultingCollection, Message *firstMsg, va_list additionalMessages) {
	return hom_returnWhere(objects, count, cleanup, resultingCollection, YES, firstMsg, additionalMessages);
}

id hom_rejectWhere(id *objects, unsigned count, BOOL cleanup, Class resultingCollection, Message *firstMsg, va_list additionalMessages) {
	return hom_returnWhere(objects, count, cleanup, resultingCollection, NO, firstMsg, additionalMessages);
}

//static inline id hom_returnSingleWhere(id *objects, unsigned count, BOOL resultVal, Message *firstMsg, va_list additionalMessages) __attribute__((always_inline));
static inline id hom_returnSingleWhere(id *objects, unsigned count, BOOL cleanup, BOOL resultVal, Message *firstMsg, va_list additionalMessages) {
	Message				*msg;
	unsigned			i;
	va_list				messages;
	
	
	// Loop through all our objects, and send it all the messages passed to us.
	// All messages except the last are assumed to return id, and the last one should return a BOOL
	// (although any integer value will work).
	for (i = 0U; i < count; i++) {
		id	obj = objects[i];
		
		// Send every message except the last one to our object while setting obj
		// to be the result of the last message.
		// NOTE: We don't verify that the receiver responds to the actual message like -select does
		// which give us a nice speed boost, but may also cause an exception to be thrown.
		// TODO: Cache the IMPs of -[Message selector], -[Message argumentsSize] and -[Message arguments].
		//for (j = 0U; j < (msgCount - 1U); j++) {
		obj = objc_msgSendv(obj, [firstMsg selector], [firstMsg argumentsSize], [firstMsg arguments]);
		
		va_copy(messages, additionalMessages);
		
		while ((msg = va_arg(messages, Message *)))
			obj = objc_msgSendv(obj, [msg selector], [msg argumentsSize], [msg arguments]);
		
		va_end(messages);
		
		//msg = objectAtIndexIMP(messages, @selector(objectAtIndex:), msgCount - 1);
		
		//if (((hom_BOOL_msgSendv)objc_msgSendv)(obj, [msg selector], [msg argumentsSize], [msg arguments]) == resultValue)
		//	[result addObject:object];
		if ((BOOL)(int)obj == resultVal) {
			obj = objects[i];
			
			if (cleanup) {
				free(objects);
				va_end(additionalMessages);
			}
			
			return obj;
		}
	}
	
	if (cleanup) {
		free(objects);
		va_end(additionalMessages);
	}
	
	return nil;
}

id hom_selectSingleWhere(id *objects, unsigned count, BOOL cleanup, Message *firstMsg, va_list additionalMessages) {
	return hom_returnSingleWhere(objects, count, cleanup, YES, firstMsg, additionalMessages);
}

/*id hom_rejectSingleWhere(id *objects, unsigned count, BOOL cleanup, Message *firstMsg, va_list additionalMessages) {
	return hom_returnSingleWhere(objects, count, cleanup, NO, firstMsg, additionalMessages);
}*/
